//   Copyright [2008] [Joel Riedesel]
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.tree.*;
import javax.swing.text.*;
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;
import javax.swing.undo.UndoManager;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.geom.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import com.xerox.amazonws.sdb.*;

import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;

public class Browser extends JFrame
  implements ActionListener, WindowListener, ListSelectionListener,
	     MouseListener {

  JMenu fileMenu;
  JMenuItem loginItem;
  JMenuItem exitItem;

  JButton refreshDomainsB, createDomainB;
  JButton deleteSelectedItemsB, createItemB;

  private SimpleDB sdb;

  SimpleListModel domains = new SimpleListModel();
  JList domainsList;
  SimpleListModel items = new SimpleListModel();
  JList itemsList;

  public Browser() {
    setTitle("Amazon SimpleDB Browser");
    getContentPane().setLayout(new BorderLayout());
    createMenuBar();

    domainsList = new JList(domains);
    domainsList.addListSelectionListener(this);
    domainsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    domainsList.addMouseListener(this);
    itemsList = new JList(items);
    itemsList.addMouseListener(this);

    JPanel domainsPanel = new JPanel(new BorderLayout());
    JPanel dlblP = new JPanel(new FlowLayout(FlowLayout.CENTER));
    dlblP.add(new JLabel("Domains"));
    domainsPanel.add(dlblP, BorderLayout.NORTH);
    JPanel domainsButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
    refreshDomainsB = new JButton("Refresh");
    domainsButtonPanel.add(refreshDomainsB);
    refreshDomainsB.addActionListener(this);
    createDomainB = new JButton("New");
    domainsButtonPanel.add(createDomainB);
    createDomainB.addActionListener(this);
    domainsPanel.add(domainsButtonPanel, BorderLayout.SOUTH);
    domainsPanel.add(new JScrollPane(domainsList), BorderLayout.CENTER);

    JPanel itemsPanel = new JPanel(new BorderLayout());
    JPanel ilblP = new JPanel(new FlowLayout(FlowLayout.CENTER));
    ilblP.add(new JLabel("Items"));
    itemsPanel.add(ilblP, BorderLayout.NORTH);
    JPanel itemsButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
    deleteSelectedItemsB = new JButton("Delete Selected Items");
    itemsButtonPanel.add(deleteSelectedItemsB);
    deleteSelectedItemsB.addActionListener(this);
    createItemB = new JButton("New");
    itemsButtonPanel.add(createItemB);
    createItemB.addActionListener(this);
    itemsPanel.add(itemsButtonPanel, BorderLayout.SOUTH);
    itemsPanel.add(new JScrollPane(itemsList), BorderLayout.CENTER);

    JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
				     domainsPanel,
				     itemsPanel);
    getContentPane().add(pane);
  }

  class SDBDomain implements Comparable {
    Domain domain;
    ArrayList items = new ArrayList();

    SDBDomain(Domain d) {
      this.domain = d;
    }
    public String toString() {
      return(domain.getName());
    }
    public int compareTo(Object o) {
      return(toString().compareToIgnoreCase(o.toString()));
    }
  }

  class SDBItem implements Comparable {
    Item item;
    List attributes;

    SDBItem(Item i) {
      this.item = i;
    }
    List getAttributes() {
      if(attributes == null) {
	attributes = new ArrayList();
	try {
	  List iatts = item.getAttributes();
	  for(int i=0; i<iatts.size(); i++) {
	    ItemAttribute att = (ItemAttribute)iatts.get(i);
	    SDBItemAttribute sdbatt = new SDBItemAttribute(att);
	    attributes.add(sdbatt);
	  }
	  Collections.sort(attributes);
	} catch(Exception ex) {
	  ex.printStackTrace();
	}
      }
      return(attributes);
    }
    public String toString() {
      return(item.getIdentifier());
    }
    public boolean equals(Object x) {
      if(x instanceof SDBItem) {
	SDBItem itemx = (SDBItem)x;
	return(item.getIdentifier().equals(itemx.item.getIdentifier()));
      }
      return(false);
    }
    public int compareTo(Object o) {
      return(toString().compareToIgnoreCase(o.toString()));
    }
  }

  class SDBItemAttribute implements Comparable {
    ItemAttribute attribute;
    SDBItemAttribute(ItemAttribute att) {
      this.attribute = att;
    }
    public String toString() {
      return(attribute.getName());
    }
    public int compareTo(Object o) {
      return(attribute.getName().compareToIgnoreCase(o.toString()));
    }
  }

  void getDomains() {
    this.domains.clear();
    try {
      ListDomainsResult r = sdb.listDomains();
      while(r != null) {
	List domains = r.getDomainList();
	for(int i=0; i<domains.size(); i++) {
	  Domain d = (Domain)domains.get(i);
	  SDBDomain sdbd = new SDBDomain(d);
	  this.domains.add(sdbd);
	}
	String nexttoken = r.getNextToken();
	if(nexttoken != null) {
	  r = sdb.listDomains(nexttoken);
	} else {
	  r = null;
	}
      }
      this.domains.sort();
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }

  void getItems(final SDBDomain d) {
    items.setContents(d.items);
    if(d.items.size() == 0) {
      try {
	JPanel statusPanel = new JPanel(new FlowLayout());
	final JLabel retrievingL = new JLabel("Items retrieved so far: 0");
	statusPanel.add(retrievingL);
	JPanel bPanel = new JPanel();
	JButton cancelB = new JButton("Cancel");
	bPanel.add(cancelB);
	final ArrayList dataV = new ArrayList();
	final JDialog dlg = new JDialog(this, "Retrieving", false);
	cancelB.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
	      dataV.add(new Boolean(true));
	    }
	  });
	JPanel dlgPanel = new JPanel(new BorderLayout());
	dlgPanel.setBorder(new EmptyBorder(10,10,10,10));
	dlgPanel.add(statusPanel, BorderLayout.CENTER);
	dlgPanel.add(bPanel, BorderLayout.SOUTH);
	dlg.getContentPane().add(dlgPanel, BorderLayout.CENTER);
	dlg.pack();
	dlg.setLocationRelativeTo(this);
	dlg.setVisible(true);

	final SimpleListModel theitems = items;
	Runnable itemgetter = new Runnable() {
          public void run() {
	    try {
	    int count = 0;
	    QueryResult qr = d.domain.listItems(null, null, 100);
	    while((qr != null) && (dataV.size() == 0)) {
	      List sdbitems = qr.getItemList();
	      //System.out.println("item list count: " + sdbitems.size());
	      for(int i=0; i<sdbitems.size(); i++) {
		Item item = (Item)sdbitems.get(i);
		SDBItem sdbitem = new SDBItem(item);
		theitems.add(sdbitem);
	      }
	      count = count + sdbitems.size();
	      retrievingL.setText("Items retrieved so far: " + count);
	      dlg.pack();
	      String nexttoken = qr.getNextToken();
	      //System.out.println("nexttoken: " + nexttoken);
	      if(nexttoken != null) {
		qr = d.domain.listItems(null, nexttoken, 100);
	      } else {
		qr = null;
	      }
	    }
	    } catch(Exception ex1) {
	      ex1.printStackTrace();
	    }
	    theitems.sort();
	    dlg.dispose();
	  }};
	new Thread(itemgetter).start();
	itemsList.invalidate();
	repaint();
	
      } catch(Exception ex) {
	ex.printStackTrace();
      }
    }
    int[] indices = new int[0];
    itemsList.setSelectedIndices(indices);
  }





  private void createMenuBar() {
    JMenuBar mb = new JMenuBar();
    
    fileMenu = new JMenu("File");
    mb.add(fileMenu);
    loginItem = new JMenuItem("Login to SimpleDB Account");
    fileMenu.add(loginItem);
    exitItem = new JMenuItem("Exit");
    fileMenu.add(exitItem);

    setJMenuBar(mb);
    
    loginItem.addActionListener(this);
    exitItem.addActionListener(this);

  }

  void performLogin() {
    Properties awsprops = new Properties();
    File awspropsf = new File(System.getProperty("user.home"));
    awspropsf = new File(awspropsf, ".awsproperties");
    loadProperties(awspropsf, awsprops);
    String key = "";
    String skey = "";
    key = awsprops.getProperty("AWSKey");
    skey = awsprops.getProperty("AWSSKey");
    
    JTextField keyTF = new JTextField(key, 20);
    JPasswordField skeyTF = new JPasswordField(skey, 20);
    JPanel keyPanel = new JPanel(new TableLayout(0, 2));
    keyPanel.add(new JLabel("AWS Key:"));
    keyPanel.add(keyTF);
    keyPanel.add(new JLabel("AWS Secret Key:"));
    keyPanel.add(skeyTF);

    JPanel bPanel = new JPanel();
    JButton okB = new JButton("Login");
    bPanel.add(okB);
    JButton cancelB = new JButton("Cancel");
    bPanel.add(cancelB);
    final ArrayList dataV = new ArrayList();
    final JDialog dlg = new JDialog(this, "Login", true);
    okB.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  dataV.add(new Boolean(true));
	  dlg.dispose();
	}
      });
    cancelB.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  dlg.dispose();
	}
      });
    JPanel dlgPanel = new JPanel(new BorderLayout());
    dlgPanel.setBorder(new EmptyBorder(10,10,10,10));
    dlgPanel.add(keyPanel, BorderLayout.CENTER);
    dlgPanel.add(bPanel, BorderLayout.SOUTH);
    dlg.getContentPane().add(dlgPanel, BorderLayout.CENTER);
    dlg.pack();
    dlg.setLocationRelativeTo(this);
    dlg.setVisible(true);
    
    if(dataV.size() == 0) return;  // cancelled


    key = keyTF.getText();
    skey = skeyTF.getText();

    sdb = new SimpleDB(key, skey);
    sdb.setSignatureVersion(0);

    awsprops.setProperty("AWSKey", key);
    awsprops.setProperty("AWSSKey", skey);
    storeProperties(awspropsf, awsprops);

    getDomains();
  }

  void showItemDetails(final SDBItem item) {
    try {
      final List atts = item.getAttributes();
      final JDialog dlg = new JDialog(this, item.toString(), false);
      final JPanel attsPanel = new JPanel(new TableLayout(0, 2));
      for(int i=0; i<atts.size(); i++) {
	SDBItemAttribute sdbitematt = (SDBItemAttribute)atts.get(i);
	final JLabel nmL = new JLabel(sdbitematt.attribute.getName());
	nmL.setToolTipText("Right click for options");
	final JTextField valTF =
	  new JTextField(sdbDecodeAttribute(sdbitematt.attribute.getValue()), 25);
	attsPanel.add(nmL);
	attsPanel.add(valTF);
	nmL.addMouseListener(new MouseAdapter() {
	    public void mouseClicked(MouseEvent e) {
	      JPopupMenu pmenu = new JPopupMenu("Menu");
	      JMenuItem deleteItem = new JMenuItem("Delete Attribute");
	      deleteItem.addActionListener(new ActionListener() {
		  public void actionPerformed(ActionEvent e) {
		    attsPanel.remove(nmL);
		    attsPanel.remove(valTF);
		    attsPanel.invalidate();
		    attsPanel.validate();
		    dlg.validate();
		    dlg.repaint();
		  }});
	      pmenu.add(deleteItem);
	      pmenu.show(nmL, e.getX(), e.getY());
	    }});
      }
      JPanel bPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
      JButton addB = new JButton("Add Attribute");
      bPanel.add(addB);
      addB.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    String s =
	      JOptionPane.showInputDialog(dlg, "Enter Attribute Name:");
	    if((s == null) || "".equals(s)) return;
	    final JLabel nmL = new JLabel(s);
	    nmL.setToolTipText("Right click for options");
	    final JTextField valTF = new JTextField(25);
	    attsPanel.add(nmL);
	    attsPanel.add(valTF);
	    nmL.addMouseListener(new MouseAdapter() {
		public void mouseClicked(MouseEvent e) {
		  JPopupMenu pmenu = new JPopupMenu("Menu");
		  JMenuItem deleteItem = new JMenuItem("Delete Attribute");
		  deleteItem.addActionListener(new ActionListener() {
		      public void actionPerformed(ActionEvent e) {
			attsPanel.remove(nmL);
			attsPanel.remove(valTF);
			attsPanel.invalidate();
			attsPanel.validate();
			dlg.validate();
			dlg.repaint();
		      }});
		  pmenu.add(deleteItem);
		  pmenu.show(nmL, e.getX(), e.getY());
		}});
	    attsPanel.invalidate();
	    attsPanel.validate();
	    dlg.validate();
	    dlg.pack();
	    dlg.repaint();
	  }});
      JButton okB = new JButton("Update");
      bPanel.add(okB);
      okB.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    try {
	      ArrayList datts = new ArrayList();
	      for(int i=0; i<atts.size(); i++) {
		SDBItemAttribute satt = (SDBItemAttribute)atts.get(i);
		datts.add(satt.attribute);
	      }
	      if(datts.size() > 0) {
		item.item.deleteAttributes(datts);
	      }
	      item.attributes = new ArrayList();
	      ArrayList aatts = new ArrayList();
	      Component[] comps = attsPanel.getComponents();
	      for(int i=0; i<comps.length; i+=2) {
		JLabel nmL = (JLabel)comps[i];
		JTextField valTF = (JTextField)comps[i+1];
		String val = valTF.getText();
		if("".equals(valTF)) continue;
		ItemAttribute iatt =
		  new ItemAttribute(nmL.getText(), sdbEncodeAttribute(val),
				    false);
		aatts.add(iatt);
		SDBItemAttribute sdbatt = new SDBItemAttribute(iatt);
		item.attributes.add(sdbatt);
	      }
	      Collections.sort(item.attributes);
	      item.item.putAttributes(aatts);
	      dlg.dispose();
	    } catch(Exception ex) {
	      ex.printStackTrace();
	    }
	  }
	});
      JButton cancelB = new JButton("Cancel");
      bPanel.add(cancelB);
      cancelB.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    dlg.dispose();
	  }
	});
      JPanel dlgPanel = new JPanel(new BorderLayout());
      dlgPanel.setBorder(new EmptyBorder(10,10,10,10));
      dlgPanel.add(attsPanel, BorderLayout.CENTER);
      dlgPanel.add(bPanel, BorderLayout.SOUTH);
      dlg.getContentPane().add(dlgPanel, BorderLayout.CENTER);
      dlg.pack();
      dlg.setLocationRelativeTo(this);
      dlg.setVisible(true);

    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }

  void deleteItems(SDBDomain domain, int[] idxs) {
    try {
      ArrayList items = new ArrayList();
      for(int i=0; i<idxs.length; i++) {
	items.add(domain.items.get(idxs[i]));
      }
      for(int i=0; i<items.size(); i++) {
	SDBItem item = (SDBItem)items.get(i);
	domain.domain.deleteItem(item.item.getIdentifier());
	domain.items.remove(item);
      }
      getItems(domain);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }

  String sdbEncodeAttribute(String att) {
    StringBuilder sb = new StringBuilder();
    int len = att.length();
    for(int i=0; i<len; i++) {
      char c = att.charAt(i);
      if('\\' == c) {
	sb.append('\\');
	sb.append('\\');
      } else if('\'' == c) {
	sb.append('\\');
	sb.append('\'');
      } else {
	sb.append(c);
      }
    }
    return(sb.toString());
  }

  String sdbDecodeAttribute(String att) {
    StringBuilder sb = new StringBuilder();
    int len = att.length();
    for(int i=0; i<len; i++) {
      char c = att.charAt(i);
      if('\\' == c) {
	char c1 = att.charAt(i+1);
	if('\\' == c1) {
	  i++;
	  sb.append('\\');
	} else if('\'' == c1) {
	  i++;
	  sb.append('\'');
	} else {
	  // ??? shouldn't occur
	}
      } else {
	sb.append(c);
      }
    }
    return(sb.toString());
  }

  void loadProperties(File f, Properties props) {
    try {
      FileInputStream fis = new FileInputStream(f);
      props.load(fis);
      fis.close();
    } catch(Exception ex) {}
  }
  void storeProperties(File f, Properties props) {
    try {
      FileOutputStream fos = new FileOutputStream(f);
      props.store(fos, null);
      fos.close();
    } catch(Exception ex) {}
  }

  public void valueChanged(ListSelectionEvent e) {
    if(e.getValueIsAdjusting()) return;
    Object source = e.getSource();
    if(source == domainsList) {
      SDBDomain sdbd = (SDBDomain)domainsList.getSelectedValue();
      if(sdbd == null) return;
      getItems(sdbd);
    }
  }


  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == loginItem) {
      performLogin();
    } else if(source == exitItem) {
      exit();
    } else if(sdb == null) {
      return;
    } else if(source == refreshDomainsB) {
      getDomains();
    } else if(source == createDomainB) {
      try {
	String s = JOptionPane.showInputDialog(this, "Enter Domain Name:");
	if((s == null) || "".equals(s)) return;
	for(int i=0; i<domains.size(); i++) {
	  SDBDomain d = (SDBDomain)domains.get(i);
	  if(s.equals(d.toString())) {
	    JOptionPane.showMessageDialog(this,
					  "Domain already exists",
					  "Create Error",
					  JOptionPane.ERROR_MESSAGE);
	    return;
	  }
	}
	Domain d = sdb.createDomain(s);
	getDomains();
      } catch(Exception ex) {
	ex.printStackTrace();
      }
    } else if(source == deleteSelectedItemsB) {
      try {
	SDBDomain domain = (SDBDomain)domainsList.getSelectedValue();
	if(domain == null) {
	  JOptionPane.showMessageDialog(this,
					"A domain must be selected",
					"Domain Error",
					JOptionPane.ERROR_MESSAGE);
	  return;
	}
	int[] idxs = itemsList.getSelectedIndices();
	deleteItems(domain, idxs);
      } catch(Exception ex) {
	ex.printStackTrace();
      }
    } else if(source == createItemB) {
      try {
	SDBDomain domain = (SDBDomain)domainsList.getSelectedValue();
	if(domain == null) {
	  JOptionPane.showMessageDialog(this,
					"A domain must be selected",
					"Domain Error",
					JOptionPane.ERROR_MESSAGE);
	  return;
	}
	String s = JOptionPane.showInputDialog(this, "Enter Item Name:");
	if((s == null) || "".equals(s)) return;
	Item item = domain.domain.getItem(s);
	SDBItem sdbitem = new SDBItem(item);
	if(!domain.items.contains(sdbitem)) {
	  domain.items.add(sdbitem);
	  Collections.sort(domain.items);
	}
	getItems(domain);
	int idx = domain.items.indexOf(sdbitem);
	itemsList.setSelectedIndex(idx);
	itemsList.ensureIndexIsVisible(idx);
      } catch(Exception ex) {
	ex.printStackTrace();
      }
    }
  }

  public void mouseClicked(MouseEvent e) {
    Object obj = e.getSource();
    if((obj == itemsList) && (e.getButton() != 1)) {
      final int idx = itemsList.locationToIndex(e.getPoint());
      final SDBItem item = (SDBItem)items.get(idx);
      final SDBDomain domain = (SDBDomain)domainsList.getSelectedValue();
      itemsList.addSelectionInterval(idx, idx);
      //itemsList.setSelectedIndex(idx);
      JPopupMenu pmenu = new JPopupMenu("Details");
      JMenuItem detailsItem = new JMenuItem("Item Attributes");
      detailsItem.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    int[] idxs = itemsList.getSelectedIndices();
	    for(int i=0; i<idxs.length; i++) {
	      SDBItem itemx = (SDBItem)items.get(idxs[i]);
	      showItemDetails(itemx);
	    }
          }});
      pmenu.add(detailsItem);
      JMenuItem deleteItem = new JMenuItem("Delete Item");
      deleteItem.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    int[] idxs = itemsList.getSelectedIndices();
	    deleteItems(domain, idxs);
          }});
      pmenu.add(deleteItem);
      pmenu.show(itemsList, e.getX(), e.getY());
    } else if((obj == domainsList) && (e.getButton() != 1)) {
      int idx = domainsList.locationToIndex(e.getPoint());
      final SDBDomain domain = (SDBDomain)domains.get(idx);
      domainsList.setSelectedIndex(idx);
      JPopupMenu pmenu = new JPopupMenu("Menu");
      JMenuItem refreshItem = new JMenuItem("Refresh Items");
      refreshItem.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    domain.items.clear();
	    getItems(domain);
	  }});
      JMenuItem deleteItem = new JMenuItem("Delete Domain");
      deleteItem.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    try {
	      sdb.deleteDomain(domain.domain);
	      getDomains();
	    } catch(Exception ex) {
	      ex.printStackTrace();
	    }
	  }});
      pmenu.add(refreshItem);
      pmenu.add(deleteItem);
      pmenu.show(domainsList, e.getX(), e.getY());
    }
  }
  public void mouseEntered(MouseEvent e) {
  }
  public void mouseExited(MouseEvent e) {
  }
  public void mousePressed(MouseEvent e) {
  }
  public void mouseReleased(MouseEvent e) {
  }

  public void exit() {
    System.exit(1);
  }

  public void windowClosing(WindowEvent e) {
    exit();
  }
  public void windowActivated(WindowEvent e) {
  }
  public void windowDeactivated(WindowEvent e) {
  }
  public void windowOpened(WindowEvent e) {
  }
  public void windowIconified(WindowEvent e) {
  }
  public void windowDeiconified(WindowEvent e) {
  }
  public void windowClosed(WindowEvent e) {
  }

  public static void main(String[] args) {
    Browser b = new Browser();
    b.setSize(500, 500);
    b.setLocation(100, 300);
    b.setVisible(true);
  }
}

